/**
 * BrandBigData.com Inc.
 * Copyright (c) 2016 All Rights Reserved.
 */
package com.qibao.toolkit.protocol.protocols;

import com.qibao.toolkit.facade.exception.ServiceException;
import com.qibao.toolkit.protocol.exception.ProtocolException;
import com.qibao.toolkit.protocol.facade.Protocol;
import com.qibao.toolkit.protocol.facade.ProtocolConfiguration;
import com.qibao.toolkit.protocol.facade.Request;
import com.qibao.toolkit.protocol.facade.Response;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpMethod;

/**
 * @author xumin
 * @version $Id:HttpGetProtocol.java, v0.1 2017/6/21 13:25 xumin
 */
public abstract class HttpProtocol implements Protocol {
    private static final Logger logger = LoggerFactory.getLogger(HttpProtocol.class);

    protected HttpMethod method;
    private static Executor executor;

    static {
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
        connManager.setDefaultMaxPerRoute(20);
        connManager.setMaxTotal(200);
        HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build();
        executor = Executor.newInstance(httpClient);
    }

    public HttpProtocol(HttpMethod method) {
        this.method = method;
    }

    @Override
    public Response sendRequest(Request request, ProtocolConfiguration configuration) throws ProtocolException {
        request = preSendRequest(request);
        Response response = doSend(request, configuration);
        response = preReturnResponse(response);
        return response;
    }

    protected Response doSend(Request request, ProtocolConfiguration configuration) {
        String url = prepareUrl(request);
        if (!url.contains("http")) {
            url = "http://" + url;
        }
        logger.info("开始请求远程节点,url为:{}", url);
        org.apache.http.client.fluent.Request httpClientRequest = HttpMethod.GET
                .equals(method) ? org.apache.http.client.fluent.Request.Get(url) : org.apache.http.client.fluent.Request.Post(url);
        prepareHeaders(request, httpClientRequest);
        prepareBodyForm(request, httpClientRequest);
        prepareRequestBody(request, httpClientRequest);
        setProxy(httpClientRequest);
        if (null != configuration) {
            if (null != configuration.getConnectionTimeout() && configuration.getConnectionTimeout() > 0) {
                httpClientRequest.connectTimeout(configuration.getConnectionTimeout());
            }
            if (null != configuration.getTransferTimeout() && configuration.getTransferTimeout() > 0) {
                httpClientRequest.socketTimeout(configuration.getTransferTimeout());
            }
        }
        try {
            HttpResponse httpResponse = executor.execute(httpClientRequest).returnResponse();
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            Response response = new Response();
            if (statusCode != HttpStatus.SC_OK) {
                String errorMsg = "invoke remote service error,url:" + url + " status:" + statusCode + " errorMsg:" + EntityUtils.toString(httpResponse.getEntity());
                logger.error(errorMsg);
                throw new ProtocolException(errorMsg);
            } else {
                response.setSuccess(true);
                response.setContent(EntityUtils.toString(httpResponse.getEntity(), "utf-8"));
            }
            logger.debug(
                    "\n**********************************************************************************\n接口: {}\n请求: {}\n响应: {}\n**********************************************************************************",
                    url, request.getRequestBody(), response.getContent());
            return response;
        } catch (IOException e) {
            logger.error("请求异常 url:{}", url, e);
            throw new ProtocolException(e.getMessage(), e);
        }
    }

    /**
     * 发送请求之前，对请求进行处理。
     * <p>
     * 不同的请求协议，请求格式不会一致，上层调用都方式一致。格式转换具体的子类去实现
     * </p>
     *
     * @param request
     * @return
     */
    protected abstract Request preSendRequest(Request request);

    /**
     * 返回响应之前，对响应进行额外的处理
     * <p>
     * <li>比如汇法网，返回格式为xml,由汇法网协议子类转换为程序使用的json格式。</li>
     * <li>gxp协议，验证gxp头是否合法，并处理gxp上一节点返回的报文格式</li>
     * </p>
     *
     * @param response
     * @return
     */
    protected abstract Response preReturnResponse(Response response);

    protected String prepareUrl(Request request) {
        if (HttpMethod.GET.equals(method) || (HttpMethod.POST.equals(method) && StringUtils.isEmpty(request.getRequestBody()))) {
            try {
                URIBuilder builder = new URIBuilder(request.getUrl());
                request.getQueryParameters().forEach((name, value) -> {
                    if (StringUtils.isEmpty(name) || null == value) {
                        return;
                    }
                    builder.addParameter(name, value);
                });
                return builder.build().toString();
            } catch (URISyntaxException e) {
                throw new ServiceException(e.getMessage(), e);
            }
        }
        return request.getUrl();
    }

    protected void prepareRequestBody(Request request, org.apache.http.client.fluent.Request httpClientRequest) {
        if (HttpMethod.GET.equals(method) || StringUtils.isEmpty(request.getRequestBody())) {
            return;
        }
        if (requestBodyIsXml(request.getRequestBody())) {
            //xml
            httpClientRequest.bodyString(request.getRequestBody(), ContentType.APPLICATION_XML);
        } else {
            httpClientRequest.bodyString(request.getRequestBody(), ContentType.APPLICATION_JSON);
        }
    }

    protected boolean requestBodyIsXml(String requestBody) {
        return StringUtils.isNotEmpty(requestBody) && requestBody.trim().startsWith("<");
    }

    protected void prepareBodyForm(Request request, org.apache.http.client.fluent.Request httpClientRequest) {
        if (HttpMethod.POST.equals(method) && StringUtils.isEmpty(request.getRequestBody())) {
            List<NameValuePair> nameValuePairs = Lists.newArrayList();
            request.getQueryParameters().forEach((name, value) -> {
                if (StringUtils.isEmpty(name) || null == value) {
                    return;
                }
                nameValuePairs.add(new BasicNameValuePair(name, value));
            });
            httpClientRequest.bodyForm(nameValuePairs, Charsets.UTF_8);
        }
    }

    protected void prepareHeaders(Request request, org.apache.http.client.fluent.Request getRequest) {
        Map<String, String> headers = request.getHeaders();
        if (headers == null || headers.size() == 0) {
            return;
        }
        request.getHeaders().forEach((name, value) -> {
            if (StringUtils.isEmpty(name) || null == value) {
                return;
            }
            getRequest.addHeader(name, value);
        });
    }

    protected void setProxy(org.apache.http.client.fluent.Request request) {

    }
}
